package com.vpen.springsecuritytest.config;


import com.vpen.springsecuritytest.handler.MyAccessDeniedHandler;
import com.vpen.springsecuritytest.handler.MyAuthenticationSuccessHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;



import javax.annotation.Resource;


/**
 * -@Version 1.0
 * -@Author:Vpen
 * -@Date:2021/3/11 21:53
 * -@Content:
 */
@Configuration
@EnableWebSecurity // 启用Spring Security的Web安全支持
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {
//    FilterSecurityInterceptor 方法级的权限过滤链，位于最后一层
//    ExceptionTranslationFilter 是个异常的过滤器，用来处理认证授权抛出的异常
//    UsernamePasswordAuthenticationFilter 对/Post请求做拦截，校验表单中的用户名，密码
//DelegatingFilterProxy
//    FilterSecurityInterceptor
    /**
     * 作者:Vpen
     * 描述:
     * configure(HttpSecurity)方法定义了哪些URL路径应该被保护，哪些不应该。
     * 具体来说，“/”和"/page/page1.html"路径被配置为不需要任何身份验证。
     * 所有其他路径必须经过身份验证。
     * */
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        //关闭csrf安全防护
        http.csrf().disable();

        /* *
         * 作者:Vpen
         * 描述:当提供了自定的configure之后，删除super.configure的时候，所有的默认请求会清空
         *
         * http.formLogin().loginProcessingUrl(String url)
         * 用户登录逻辑的请求地址，Security提供的处理登录逻辑的控制器，是path监听的软编码，此方法可以动态配置监听地址
         * 只要配置地址loginProcessingUrl("/SubmitLogin")和页面(action="/SubmitLogin")的请求地址一致,即可完成登录
         * 默认登录成功返回 "/", 如果有前置请求默认返回前置请求，失败 返回登录页"login.html?error"
         *
         * */


        // 配置登录相关(认证)
        http.formLogin()
                .usernameParameter("username")// from表单里的input的name=username
                .passwordParameter("password")// from表单里的input的name=password
                .loginProcessingUrl("/SubmitLogin")  //用户登录逻辑的请求地址
                .loginPage("/login.html")// 登录的页面、未登录会跳转到的页面
                // 自定义登录成功跳转的页面，默认"/";响应重定向；alwaysUse=true的话就会忽略前置请请求，
                // 不管是点哪个请求需要登录的URL都会总是跳转到指定的页面
//                .defaultSuccessUrl("/success.html")
//                .successForwardUrl("/success.html")   // 转发
                .successHandler(new MyAuthenticationSuccessHandler("/success.html",true))// 使用自定义的请求处理控制逻辑，实现登录成功后的请求处理(重定向和转发都可以)
                .failureForwardUrl("/")
                ;



        // 需要登录才能访问(授权)
        // 权限的配置是线性的，从开始的配置位置开始校验，成功立刻返回；失败继续后续校验
        // 如：登录校验成功，后续.anyRequest().authenticated()失效
        http.authorizeRequests()
                // "/"和"/page/page1.html"所有人都可以访问(请求放行)
                .antMatchers("/","/page/page1.html").permitAll()
                .antMatchers("/login.html").anonymous() // 只允许匿名登录

                // 基于资源的权限管理
                // hasAuthority("xxx") == access("hasAuthority('xxx')")
                // hasAnyAuthority("xxx","yyy")
                .antMatchers("/res/admin/read").hasAuthority("admin:read")
                .antMatchers("/res/admin/write").hasAuthority("admin:write")
                .antMatchers("/res/guest/write").hasAuthority("guest:write")
                .antMatchers("/res/guest/read").hasAnyAuthority("guest:write","admin:read")

                // 基于角色的权限管理
                // hasRole("xxx")  ==  access("hasRole('xxx')")
                // hasAnyRole("xxx","yyy")
                .antMatchers("/admin/read").hasRole("管理员") // 只有是管理员才能访问，在UserDetailsService中定义
                .antMatchers("/guest/read").hasAnyRole("管理员","访客") // 其中任意一个都可以访问
                .antMatchers("/guest/write").hasRole("访客")

                // 除了上面的请求，其他请求都需要登录后才能访问
                .anyRequest().authenticated();


        // 配置403状态码
        http.exceptionHandling()
                .accessDeniedHandler(myAccessDeniedHandler);

//        http.exceptionHandling().accessDeniedPage("/error.html");// 指定页面

        // 退出登录
        http.logout()
                .logoutSuccessUrl("/");
//                .logoutUrl("/logout.html").permitAll();

    }

    @Resource
    MyAccessDeniedHandler myAccessDeniedHandler;
    @Resource(name = "BPE")
    BCryptPasswordEncoder passwordEncoder;
    @Bean(name = "BPE")
    public BCryptPasswordEncoder BCryptPasswordEncoder(){
        return new BCryptPasswordEncoder();
    }
}
